﻿using EnvDTE;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Xml.Linq;
using System.Xml.Serialization;

namespace Axiom.CodeManifold
{
	public class MethodDefinition : IMemento<MethodDefinition>
	{
		public MethodDefinition()
		{
			Parameters = new List<Parameter>();
			Aggregate = string.Empty;
			Comment = string.Empty;
			ReturnComment = string.Empty;
			Modifier = string.Empty;
			Name = string.Empty;
			Namespace = string.Empty;
			ReturnType = string.Empty;
			ReturnComment = string.Empty;
		}

		public MethodDefinition(CodeFunction function)
		{
			Parameters = new List<Parameter>();
			this.Name = function.Name;
			this.Namespace = function.FullName;
			this.DocComment = function.DocComment;
			this.Comment = function.Comment;
			this.XCodeFunction = new XCodeElement(function);
			this.ReturnType = function.Type.GetTypeAsString();
			foreach (CodeParameter parameter in function.Parameters)
			{
				Parameters.Add(new Parameter(parameter));
			}
		}

		public string Aggregate { get; set; }

		[XmlIgnore]
		public string ArgumentSignature
		{
			get
			{
				StringBuilder sb = new StringBuilder();
				List<string> arguments = new List<string>();

				foreach (Parameter parameter in this.Parameters)
				{
					var parametername = parameter.Name;
					var parametertype = parameter.Type;
					arguments.Add(string.Format(@"{0}  {1}", parametertype, parametername));
				}

				sb.Append(string.Join(",", arguments.ToArray()));

				return sb.ToString();
			}
			set
			{
				SetParameters(value);
			}
		}

		[XmlIgnore]
		public string Body { get; set; }

		public string Comment { get; set; }

		public string DocComment { get; set; }

		[XmlIgnore]
		public bool IsModified
		{
			get
			{
				if (Memento == null) return false;
				var a = !this.Comment.Equals(Memento.Comment);
				if (a) return a; else a |= !this.Aggregate.Equals(Memento.Aggregate);
				if (a) return a; else a |= !this.Modifier.Equals(Memento.Modifier);
				if (a) return a; else a |= !this.Name.Equals(Memento.Name);
				if (a) return a; else a |= !this.Namespace.Equals(Memento.Namespace);
				if (a) return a; else a |= !this.ReturnType.Equals(Memento.ReturnType);
				if (a) return a; else a |= !this.ReturnComment.Equals(Memento.ReturnComment);
				if (a) return a; else a |= this.Parameters.Count != Memento.Parameters.Count;
				if (a) return a;
				else
					for (int i = 0; i < this.Parameters.Count; i++)
					{
						var argA = this.Parameters[i];
						var argB = Memento.Parameters[i];
						a |= argA.Comment.Equals(argB.Comment);
						a |= argA.Modifier.Equals(argB.Modifier);
						a |= argA.Name.Equals(argB.Name);
						a |= argA.Type.Equals(argB.Type);
						if (a) return a;
					}
				return a;
			}
		}

		public bool IsNew { get { return this.Memento == null; } }

		[XmlIgnore]
		public MethodDefinition Memento { get; set; }

		public string Modifier { get; set; }

		public string Name { get; set; }

		public string Namespace { get; set; }

		public List<Parameter> Parameters { get; set; }

		public string ReturnComment { get; set; }

		public string ReturnType { get; set; }

		public string Signature
		{
			get
			{
				StringBuilder sb = new StringBuilder();
				List<string> arguments = new List<string>();

				var name = this.Name;
				var type = this.ReturnType;

				sb.AppendFormat(@"{0} {1}(", type, name);

				foreach (Parameter parameter in this.Parameters)
				{
					var parametername = parameter.Name;
					var parametertype = parameter.Type;
					arguments.Add(string.Format(@"{0}  {1}", parametertype, parametername));
				}
				sb.Append(string.Join(",", arguments.ToArray()));
				sb.Append(")");

				return sb.ToString();
			}
		}

		[XmlIgnore]
		public XCodeElement XCodeFunction { get; set; }

		public void CreateDocComment(string summary)
		{
			var sb = new StringBuilder();

			sb.AppendLine("<doc>");
			sb.AppendLine("<summary>");
			sb.AppendLine(summary);
			sb.AppendLine("</summary>");

			Parameters.ForEach(a => sb.Append(a.DocComment));

			if (!string.IsNullOrWhiteSpace(ReturnType) && ReturnType.ToLower() != "void")
			{
				sb.AppendLine(string.Format("<returns>{0}</returns>", ReturnComment));
			}

			sb.AppendLine("</doc>");

			DocComment = sb.ToString();
		}

		public void Restore()
		{
			if (Memento == null)
			{
				return;
			}
			Aggregate = Memento.Aggregate;
			Comment = Memento.Comment;
			Modifier = Memento.Modifier;
			Name = Memento.Name;
			Namespace = Memento.Namespace;
			ReturnType = Memento.ReturnType;
			ReturnComment = Memento.ReturnComment;

			this.Parameters.Clear();
			foreach (var arg in Memento.Parameters)
			{
				var param = new Parameter()
				{
					Comment = arg.Comment,
					Modifier = arg.Modifier,
					Name = arg.Name,
					Type = arg.Type,
				};

				this.Parameters.Add(param);
			}
		}

		public void SetParameters(string signature)
		{
			var arguments = new List<Parameter>();
			Parameters.Clear();

			var parts = signature.Trim().Split(new char[] { ',' });

			foreach (var part in parts)
			{
				var parameter = Regex.Split(part.Trim(), @"\s+");
				if (parameter.Count() != 2)
				{
					continue;
				}
				Parameters.Add(new Parameter()
				{
					Name = parameter[1],
					Type = parameter[0],
				});
			}
		}

		public void Store()
		{
			Memento = new MethodDefinition()
			{
				Aggregate = this.Aggregate,
				Comment = this.Comment,
				Modifier = this.Modifier,
				Name = this.Name,
				Namespace = this.Namespace,
				ReturnType = this.ReturnType,
				ReturnComment = this.ReturnComment,
			};
			Memento.Parameters.Clear();
			foreach (var arg in this.Parameters)
			{
				var param = new Parameter()
				{
					Comment = arg.Comment,
					Modifier = arg.Modifier,
					Name = arg.Name,
					Type = arg.Type,
				};

				Memento.Parameters.Add(param);
			}
		}
	}

	public class XCodeElement
	{

		public XCodeElement(CodeFunction element)
		{
			this.FunctionKind = element.FunctionKind;
			this.Access = element.Access;
			this.Type = element.Type;
			this.CodeType = element.Type.CodeType;
			this.FullTypeName = element.Type.AsFullName;
		}

		public XCodeElement(CodeParameter element)
		{
			this.Type = element.Type;
			this.CodeType = element.Type.CodeType;
			this.FullTypeName = element.Type.AsFullName;
		}

		public vsCMAccess Access { get; set; }

		public CodeType CodeType { get; set; }

		public string FullTypeName { get; set; }

		public vsCMFunction FunctionKind { get; set; }

		public CodeTypeRef Type { get; set; }
	}
}
